#include "map.h"
#include "globals.h"
#include "graphics.h"
#include "constants.h"
#include "network.h"
#include "packetstream.h"
#include "filestream.h"
#include "stats.h"
#include "playserver.h"
#include "player.h"

// Constructor for the map editor: new map
CGMap::CGMap(const stringc &TFilename, int TWidth, int THeight) 
{
	Init();

	Filename = TFilename;
	Width = TWidth;
	Height = THeight;

	AllocateMap();
}

// Constructor for the map editor: load map
CGMap::CGMap(const stringc &TFilename) 
{
	Init();
	
	Filename = TFilename;
}

// Constructor for maps already created in the database
CGMap::CGMap(int TMapID) 
{
	Init();

	// Set ID
	ID = TMapID;

	// Get map info
	const MapStruct *Map = StatsClass::Instance().GetMap(ID);

	// Load map
	Filename = Map->File;
	LoadMap();
}

// Destructor
CGMap::~CGMap() 
{
	// Delete map data
	FreeMap();	
}

// Initialize variables
void CGMap::Init() 
{
	ID = 0;
	NoZoneTexture = NULL;
	Tiles = NULL;
	m_ViewSize.Width = floor(800.f/(float)MAP_TILE_WIDTH/0.75f)+1;
	m_ViewSize.Height = floor(600.f/(float)MAP_TILE_HEIGHT)+1;
	m_CameraScroll.X = 0;
	m_CameraScroll.Y = 0;
	m_CursorPos.X = 0;
	m_CursorPos.Y = 0;
}

// Free memory used by the tiles
void CGMap::FreeMap() 
{
	if(Tiles) 
	{
		for(int i = 0; i < Width; i++)
			delete[] Tiles[i];
		delete[] Tiles;

		Tiles = NULL;
	}
	IndexedEvents.clear();
}

// Allocates memory for the map
void CGMap::AllocateMap() 
{
	if(Tiles)
		return;

	Tiles = new TileStruct*[Width];
	for(int i = 0; i < Width; i++) {
		Tiles[i] = new TileStruct[Height];
	}

	// Delete textures
	for(u32 i = 0; i < Textures.size(); i++)
		irrDriver->removeTexture(Textures[i]);
	Textures.clear();
}

// Updates the map and sends object updates
void CGMap::Update(u32 TDeltaTime) 
{
	ObjectUpdateTime += TDeltaTime;

	if(ObjectUpdateTime > 200) 
	{
		ObjectUpdateTime = 0;
		SendObjectUpdates();
	}
}

// Renders the map
void CGMap::Render() 
{
	position2di DrawPosition;

	int i = (float)(m_CameraScroll.X) / ((float)MAP_TILE_WIDTH * 0.75f);
	for(int di=0; di < m_ViewSize.Width; di++,i++) 
	{
		int j = (float)m_CameraScroll.Y / (float)MAP_TILE_HEIGHT;
		for(int dj=0; dj < m_ViewSize.Height; dj++,j++) 
		{
			DrawPosition.X = i*(float)MAP_TILE_WIDTH * 0.75f + (float)MAP_TILE_WIDTH * 0.5f - m_CameraScroll.X;
			DrawPosition.Y = j*(float)MAP_TILE_HEIGHT +(i%2)*(float)MAP_TILE_HEIGHT/2.0f + (float)MAP_TILE_HEIGHT * 0.5f - m_CameraScroll.Y;

			// Validate coordinate
			if(i >= 0 && i < Width && j >= 0 && j < Height) 
			{
				TileStruct& Tile = Tiles[i][j];

				if(Tile.Wall)
				{
					GraphicsClass::Instance().DrawHex(DrawPosition.X, DrawPosition.Y,MAP_TILE_WIDTH-1,MAP_TILE_HEIGHT-1,true,SColor(50,255,0,0));
				}
				else
				{
					GraphicsClass::Instance().DrawHex(DrawPosition.X, DrawPosition.Y,MAP_TILE_WIDTH-1,MAP_TILE_HEIGHT-1,true,SColor(50,180,180,180));
				}
			}
		}
	}
	
	DrawPosition.X = m_CursorPos.X * (float)MAP_TILE_WIDTH * 0.75f + (float)MAP_TILE_WIDTH * 0.5f - m_CameraScroll.X; 
	DrawPosition.Y = m_CursorPos.Y * (float)MAP_TILE_HEIGHT +(m_CursorPos.X%2)*(float)MAP_TILE_HEIGHT/2.0f + + (float)MAP_TILE_HEIGHT * 0.5f - m_CameraScroll.Y;

	GraphicsClass::Instance().DrawHex(DrawPosition.X, DrawPosition.Y,MAP_TILE_WIDTH-1,MAP_TILE_HEIGHT-1,true,SColor(50,100,100,0));
	
	char info_str[1024];
	sprintf(info_str,"TP(%d,%d), ST=%d, SP=%d, CP(%d,%d)",m_SectionPos.X,m_SectionPos.Y,m_SectionType,m_SectionPart,m_CursorPos.X,m_CursorPos.Y);
	GraphicsClass::Instance().RenderText(info_str,5,5);
}

// Renders the map for editor
void CGMap::RenderForMapEditor(bool TDrawWall, bool TDrawZone, bool TDrawPVP) 
{
	/*position2di GridPosition, DrawPosition;
	for(int i = 0; i < ViewSize.Width; i++) 
	{
		for(int j = 0; j < ViewSize.Height; j++) 
		{

			// Get the actual grid coordinate
			GridPosition.X = i + CameraScroll.X - ViewSize.Width / 2;
			GridPosition.Y = j + CameraScroll.Y - ViewSize.Height / 2;
			DrawPosition = position2di((i - ViewSize.Width / 2) * MAP_TILE_WIDTH + 400, (j - ViewSize.Height / 2) * MAP_TILE_HEIGHT + 300);
			
			if(NoZoneTexture)
				GraphicsClass::Instance().DrawCenteredImage(NoZoneTexture, DrawPosition.X, DrawPosition.Y);

			// Validate coordinate
			if(GridPosition.X >= 0 && GridPosition.X < Width && GridPosition.Y >= 0 && GridPosition.Y < Height) 
			{
				TileStruct *Tile = &Tiles[GridPosition.X][GridPosition.Y];

				// Draw texture
				if(Tile->Texture)
					GraphicsClass::Instance().DrawCenteredImage(Tile->Texture, DrawPosition.X, DrawPosition.Y);
				else if(NoZoneTexture)
					GraphicsClass::Instance().DrawCenteredImage(NoZoneTexture, DrawPosition.X, DrawPosition.Y);

				// Draw wall
				if(TDrawWall && Tile->Wall)
					GraphicsClass::Instance().RenderText("W", DrawPosition.X, DrawPosition.Y - 8, GraphicsClass::ALIGN_CENTER);

				// Draw zone
				if(!Tile->Wall) 
				{
					if(TDrawZone && Tile->Zone > 0)
						GraphicsClass::Instance().RenderText(stringc(Tile->Zone).c_str(), DrawPosition.X, DrawPosition.Y - 8, GraphicsClass::ALIGN_CENTER);

					// Draw PVP
					if(TDrawPVP && Tile->PVP)
						GraphicsClass::Instance().RenderText("PvP", DrawPosition.X, DrawPosition.Y - 8, GraphicsClass::ALIGN_CENTER, SColor(255, 255, 0, 0));
				}

				// Draw event info
				if(Tile->EventType > 0) 
				{
					stringc EventText = StatsClass::Instance().GetEvent(Tile->EventType)->ShortName + stringc(", ") + stringc(Tile->EventData);
					GraphicsClass::Instance().RenderText(EventText.c_str(), DrawPosition.X - 16, DrawPosition.Y - 16, GraphicsClass::ALIGN_LEFT, SColor(255, 0, 255, 255));
				}
			}
			else 
			{
				GraphicsClass::Instance().DrawCenteredImage(irrDriver->getTexture("textures/editor/nozone.png"), DrawPosition.X, DrawPosition.Y);
			}
		}
	}*/
}

// Sets the camera scroll position
void CGMap::SetCameraScroll(const position2di &TPosition) 
{
	m_CameraScroll = TPosition;

	if(m_CameraScroll.X < 0)
		m_CameraScroll.X = 0;
	if(m_CameraScroll.Y < 0)
		m_CameraScroll.Y = 0;
	if(m_CameraScroll.X > Width * (float)MAP_TILE_WIDTH * 0.75f)
		m_CameraScroll.X = Width * (float)MAP_TILE_WIDTH * 0.75f;
	if(m_CameraScroll.Y > Height * (float)MAP_TILE_HEIGHT)
		m_CameraScroll.Y = Height * (float)MAP_TILE_HEIGHT;
}

// Converts a grid position on the map to a screen coordinate
bool CGMap::GridToScreen(const position2di &TGridPosition, position2di &TScreenPosition) const 
{
	// Check if it's on screen
	int StartI = m_CameraScroll.X / ((float)MAP_TILE_WIDTH * 0.75f);
	int StartJ = m_CameraScroll.Y / (float)MAP_TILE_HEIGHT;

	TScreenPosition.X = TGridPosition.X * ((float)MAP_TILE_WIDTH * 0.75f) + (float)MAP_TILE_WIDTH * 0.5f - m_CameraScroll.X;
	TScreenPosition.Y = TGridPosition.Y * ((float)MAP_TILE_HEIGHT) + (TGridPosition.X%2)*(float)MAP_TILE_HEIGHT/2.0f + (float)MAP_TILE_HEIGHT * 0.5f - m_CameraScroll.Y;
	
	if(TGridPosition.X > StartI + m_ViewSize.Width || TGridPosition.Y > StartJ + m_ViewSize.Height)
		return false;

	return true;
}

// Converts a screen coordinates to position on grid
bool CGMap::ScreenToGrid(const position2di &TScreenPosition, position2di &TGridPosition)
{
	u32 SectX = (TScreenPosition.X + m_CameraScroll.X) / ((float)MAP_TILE_WIDTH * 0.75f);
	u32 SectY = (TScreenPosition.Y + m_CameraScroll.Y) / (float)MAP_TILE_HEIGHT;

	u32 SectPxlX = (TScreenPosition.X + m_CameraScroll.X) % (int)((float)MAP_TILE_WIDTH * 0.75f);
	u32 SectPxlY = (TScreenPosition.Y + m_CameraScroll.Y) % MAP_TILE_HEIGHT;

	TGridPosition.X = SectX;
	TGridPosition.Y = SectY;

	//TODO: Remove
	m_SectionPos = TGridPosition;

	if(SectX%2)
	{
		//TODO: Remove
		m_SectionType = 0;

		//TODO: Remove
		m_SectionPart = 0;

		// right side
		if(SectPxlY  >= (float)MAP_TILE_HEIGHT / 2.0f)
		{
			if(SectPxlX < (2 * (float)MAP_TILE_WIDTH * 0.25f - SectPxlY * 0.5))
			{
				//TODO: Remove
				m_SectionPart = 1;

				TGridPosition.X = SectX - 1;
				TGridPosition.Y = SectY;
			}
			else
			{
				//TODO: Remove
				m_SectionPart = 2;

				TGridPosition.X = SectX;
				TGridPosition.Y = SectY;
			}
		}
		// left side
		if(SectPxlY < (float)MAP_TILE_HEIGHT / 2.0f)
		{
			if(SectPxlX < (SectPxlY * 0.5f))
			{
				//TODO: Remove
				m_SectionPart = 3;

				TGridPosition.Y = SectY;
				TGridPosition.X = SectX - 1;   
			}
			else
			{
				//TODO: Remove
				m_SectionPart = 4;

				TGridPosition.Y = SectY - 1;
				TGridPosition.X = SectX;
			}
		}
	}
	else
	{
		//TODO: Remove
		m_SectionType = 1;

		//TODO: Remove
		m_SectionPart = 5;

		// middle
		TGridPosition.Y = SectY;
		TGridPosition.X = SectX;
		// left Edge
		if(SectPxlX < ((float)MAP_TILE_WIDTH * 0.25f - SectPxlY * 0.5f))
		{
			//TODO: Remove
			m_SectionPart = 6;

			TGridPosition.Y = SectY - 1;
			TGridPosition.X = SectX - 1;
		}
		// right Edge
		if(SectPxlX < (- (float)MAP_TILE_WIDTH * 0.25f + SectPxlY * 0.5f))
		{
			//TODO: Remove
			m_SectionPart = 7;

			TGridPosition.Y = SectY;
			TGridPosition.X = SectX - 1;
		}
	}

	if(TGridPosition.X<0)
		TGridPosition.X=0;
	if(TGridPosition.X>Width)
		TGridPosition.X=Width;
	if(TGridPosition.Y<0)
		TGridPosition.Y=0;
	if(TGridPosition.Y>Height)
		TGridPosition.Y=Height;

	m_CursorPos = TGridPosition;

	return true;
}

// Saves the map to a file
int CGMap::SaveMap() 
{
	// Open file
	FileClass File;
	int Result = File.OpenForWrite((stringc("maps/") + Filename).c_str());

	if(!Result) 
	{
		printf("SaveMap: unable to open file for writing\n");
		return 0;
	}

	// Generate a list of textures used by the map
	array<ITexture *> TextureList;
	GetTextureListFromMap(TextureList);

	// Write header
	File.WriteInt(MAP_VERSION);
	File.WriteInt(Width);
	File.WriteInt(Height);

   // Write texture list
    File.WriteInt(TextureList.size());
    for(u32 i = 0; i < TextureList.size(); i++) {
		if(TextureList[i] == NULL)
			File.WriteString("none");
		else {

			// Strip path from texture name
			stringc TexturePath = TextureList[i]->getName();
			int SlashIndex = TexturePath.findLastChar("/\\", 2);
			TexturePath = TexturePath.subString(SlashIndex + 1, TexturePath.size() - SlashIndex - 1);

			File.WriteString(TexturePath.c_str());
		}
    }

	// Write no-zone texture
	if(NoZoneTexture == NULL)
		File.WriteString("none");
	else {

		// Strip path from texture name
		stringc TexturePath = NoZoneTexture->getName();
		int SlashIndex = TexturePath.findLastChar("/\\", 2);
		TexturePath = TexturePath.subString(SlashIndex + 1, TexturePath.size() - SlashIndex - 1);

		File.WriteString(TexturePath.c_str());
	}

	// Write map data
	TileStruct *Tile;
	for(int i = 0; i < Width; i++) {
		for(int j = 0; j < Height; j++) {
			Tile = &Tiles[i][j];

			// Write texture
			File.WriteInt(GetTextureIndex(TextureList, Tile->Texture));
			File.WriteInt(Tile->Zone);
			File.WriteInt(Tile->EventType);
			File.WriteInt(Tile->EventData);
			File.WriteChar(Tile->Wall);
			File.WriteChar(Tile->PVP);
		}
	}

	// Close file
	File.Close();

	return 1;
}

// Loads a map
int CGMap::LoadMap() 
{
	// Open file
	FileClass File;
	int Result = File.OpenForRead((stringc("maps/") + Filename).c_str());
	if(!Result) {
		printf("LoadMap: unable to open file for reading\n");
		return 0;
	}

	// Read header
	int MapVersion = File.ReadInt();
	Width = File.ReadInt();
	Height = File.ReadInt();
	if(Width < 5 || Width > 255 || Height < 5 || Height > 255) {
		printf("LoadMap: bad size header\n");
		return 0;
	}

	// Allocate memory
	FreeMap();
	AllocateMap();

	// Get count of textures
	int TextureCount = File.ReadInt();
	Textures.clear();

	// Change directories
	stringc OldWorkingDirectory = irrFile->getWorkingDirectory();
	irrFile->changeWorkingDirectoryTo("textures/map");

	// Read textures from map
	stringc TextureFile;
	char String[256];
	for(int i = 0; i < TextureCount; i++) {
		File.ReadString(String);

		TextureFile = String;
		if(TextureFile == "none")
			Textures.push_back(NULL);
		else
			Textures.push_back(irrDriver->getTexture(TextureFile.c_str()));
	}
	
	// Get no zone texture
	File.ReadString(String);
	TextureFile = String;
	if(TextureFile == "none")
		NoZoneTexture = NULL;
	else
		NoZoneTexture = irrDriver->getTexture(TextureFile.c_str());

	irrFile->changeWorkingDirectoryTo(OldWorkingDirectory.c_str());

	// Read map data
	TileStruct *Tile;
	for(int i = 0; i < Width; i++) {
		for(int j = 0; j < Height; j++) {
			Tile = &Tiles[i][j];

			Tile->Texture = Textures[File.ReadInt()];
			Tile->Zone = File.ReadInt();
			Tile->EventType = File.ReadInt();
			Tile->EventData = File.ReadInt();
			Tile->Wall = File.ReadChar();
			Tile->PVP = File.ReadChar();

			// Save off events that need to be indexed
			if(StatsClass::Instance().GetEvent(Tile->EventType)->Indexed) {
				IndexedEvents.push_back(IndexedEventStruct(Tile, position2di(i, j)));
			}
		}
	}

	// Close file
	File.Close();

	return 1;
}

// Builds an array of textures that are used in the map
void CGMap::GetTextureListFromMap(array<ITexture *> &TTextures) {

	TTextures.clear();

	// Go through map
	for(int i = 0; i < Width; i++) {
		for(int j = 0; j < Height; j++) {

			// Check for new textures
			if(GetTextureIndex(TTextures, Tiles[i][j].Texture) == -1) {
				TTextures.push_back(Tiles[i][j].Texture);
			}			
		}
	}
}

// Returns the index of a texture in an array
int CGMap::GetTextureIndex(array<ITexture *> &TTextures, ITexture *TTexture) {

	for(u32 i = 0; i < TTextures.size(); i++) {
		if(TTextures[i] == TTexture)
			return (int)i;
	}

	return -1;
}

// Determines if a square can be moved to
bool CGMap::CanMoveTo(const position2di &TPosition) {

	// Bounds
	if(TPosition.X < 0 || TPosition.X >= Width || TPosition.Y < 0 || TPosition.Y >= Height)
		return false;

	return !Tiles[TPosition.X][TPosition.Y].Wall;
}

// Adds an object to the map
void CGMap::AddObject(CGObject *TObject) 
{
	// Create packet for the new object
	PacketClass Packet(NetworkClass::WORLD_CREATEOBJECT);
	Packet.WriteChar(TObject->GetNetworkID());
	Packet.WriteChar(TObject->GetPosition().X);
	Packet.WriteChar(TObject->GetPosition().Y);
	Packet.WriteChar(TObject->GetType());
	switch(TObject->GetType()) 
	{
		case CGObject::GOT_PLAYER: 
		{
			PlayerClass *NewPlayer = static_cast<PlayerClass *>(TObject);
			Packet.WriteString(NewPlayer->GetName().c_str());
			Packet.WriteChar(NewPlayer->GetPortraitID());
		}
		break;
	}

	// Notify other players of the new object
	SendPacketToPlayers(&Packet);

	// Add object to map
	m_GObjectList.push_back(TObject);
}

// Removes an object from the map
void CGMap::RemoveObject(CGObject *TObject) {

	// Remove from the map
	for(list<CGObject *>::Iterator Iterator = m_GObjectList.begin(); Iterator != m_GObjectList.end(); ) {
		if(*Iterator == TObject)
			Iterator = m_GObjectList.erase(Iterator);
		else
			++Iterator;
	}

	// Create delete packet
	PacketClass Packet(NetworkClass::WORLD_DELETEOBJECT);
	Packet.WriteChar(TObject->GetNetworkID());

	// Send to everyone
	SendPacketToPlayers(&Packet);
}

// Returns the list of objects
const list<CGObject *> &CGMap::GetObjects() const {

	return m_GObjectList;
}

// Returns a list of players close to a player
void CGMap::GetClosePlayers(const PlayerClass *TPlayer, float TDistanceSquared, list<PlayerClass *> &TPlayers) {
	
	for(list<CGObject *>::Iterator Iterator = m_GObjectList.begin(); Iterator != m_GObjectList.end(); ++Iterator) {
		if((*Iterator)->GetType() == CGObject::GOT_PLAYER) {
			PlayerClass *Player = static_cast<PlayerClass *>(*Iterator);
			if(Player != TPlayer) {
				int XDelta = Player->GetPosition().X - TPlayer->GetPosition().X;
				int YDelta = Player->GetPosition().Y - TPlayer->GetPosition().Y;
				if((float)(XDelta * XDelta + YDelta * YDelta) <= TDistanceSquared) {
					TPlayers.push_back(Player);
				}
			}
		}
	}
}

// Returns the closest player
PlayerClass *CGMap::GetClosestPlayer(const PlayerClass *TPlayer, float TMaxDistanceSquared, int TState) {
	
	PlayerClass *ClosestPlayer = NULL;
	float ClosestDistanceSquared = 1e10;
	for(list<CGObject *>::Iterator Iterator = m_GObjectList.begin(); Iterator != m_GObjectList.end(); ++Iterator) {
		if((*Iterator)->GetType() == CGObject::GOT_PLAYER) {
			PlayerClass *Player = static_cast<PlayerClass *>(*Iterator);
			if(Player != TPlayer && Player->GetState() == TState) {
				int XDelta = Player->GetPosition().X - TPlayer->GetPosition().X;
				int YDelta = Player->GetPosition().Y - TPlayer->GetPosition().Y;
				float DistanceSquared = (float)(XDelta * XDelta + YDelta * YDelta);
				if(DistanceSquared <= TMaxDistanceSquared && DistanceSquared < ClosestDistanceSquared) {
					ClosestDistanceSquared = DistanceSquared;
					ClosestPlayer = Player;
				}
			}
		}
	}

	return ClosestPlayer;
}

// Sends object position information to all the clients in the map
void CGMap::SendObjectUpdates() 
{
	PacketClass Packet(NetworkClass::WORLD_OBJECTUPDATES, ENET_PACKET_FLAG_UNSEQUENCED, 1);

	// Get object count
	int ObjectCount = m_GObjectList.getSize();
	Packet.WriteChar(ObjectCount);

	for(list<CGObject *>::Iterator Iterator = m_GObjectList.begin(); Iterator != m_GObjectList.end(); ++Iterator) 
	{
		CGObject *Object = *Iterator;
		int State = 0;

		if(Object->GetType() == CGObject::GOT_PLAYER) 
		{
			PlayerClass *Player = static_cast<PlayerClass *>(Object);
			State = Player->GetState();
		}

		Packet.WriteChar(Object->GetNetworkID());
		Packet.WriteChar(State);
		Packet.WriteChar(Object->GetPosition().X);
		Packet.WriteChar(Object->GetPosition().Y);
		Packet.WriteChar(Object->GetDir());
	}

	SendPacketToPlayers(&Packet);
}

// Sends a packet to all of the players in the map
void CGMap::SendPacketToPlayers(PacketClass *TPacket, PlayerClass *ExceptionPlayer) 
{
	// Send the packet out
	for(list<CGObject *>::Iterator Iterator = m_GObjectList.begin(); Iterator != m_GObjectList.end(); ++Iterator) 
	{
		if((*Iterator)->GetType() == CGObject::GOT_PLAYER) 
		{
			PlayerClass *Player = static_cast<PlayerClass *>(*Iterator);

			if(Player != ExceptionPlayer)
				PlayServerState::Instance().SendPacketToPeer(TPacket, Player->GetPeer());
		}
	}
}

// Finds an event that matches the criteria
IndexedEventStruct *CGMap::GetIndexedEvent(int TEventType, int TEventData) {

	for(u32 i = 0; i < IndexedEvents.size(); i++) {
		IndexedEventStruct *IndexedEvent = &IndexedEvents[i];
		if(IndexedEvent->Tile->EventType == TEventType && IndexedEvent->Tile->EventData == TEventData) {
			return IndexedEvent;
		}
	}

	return NULL;
}
